/**
* @file    jsmi.h
* @author  mashaoze, rkxie
* @date    2018
* @par     Copyright (c):
*
*    Copyright 2018 MIoT,MI
*
*    Licensed under the Apache License, Version 2.0 (the "License");
*    you may not use this file except in compliance with the License.
*    You may obtain a copy of the License at
*
*        http://www.apache.org/licenses/LICENSE-2.0
*
*    Unless required by applicable law or agreed to in writing, software
*    distributed under the License is distributed on an "AS IS" BASIS,
*    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*    See the License for the specific language governing permissions and
*    limitations under the License.
*/
#include "jsmi.h"
#include "fmt.h"
#include "common/mible_memory.h"

/* --------------------------------------------------------------------- parser */
static size_t str_unescape(const char *in, size_t in_len, char* out, size_t out_len)
{
    size_t out_len_need = 0;
    size_t i = 0;

    for (i = 0; i < in_len; i++) {
        if(in[i] == '\\' && (i+1) < in_len){
            switch(in[++i]){
                case '\"': case '\\' :case '/' :
                    if(out && out_len_need < out_len){
                        out[out_len_need] = in[i];
                    }
                    break;
                case 'b' :
                    if(out && out_len_need < out_len){
                        out[out_len_need] = '\b';
                    }
                    break;
                case 'f' :
                    if(out && out_len_need < out_len){
                        out[out_len_need] = '\f';
                    }
                    break;
                case 'r' :
                    if(out && out_len_need < out_len){
                        out[out_len_need] = '\r';
                    }
                    break;
                case 'n'  :
                    if(out && out_len_need < out_len){
                        out[out_len_need] = '\n';
                    }
                    break;
                case 't' :
                    if(out && out_len_need < out_len){
                        out[out_len_need] = '\t';
                    }
                    break;
                default:
                    if(out && out_len_need < out_len){
                        out[out_len_need] = in[i-1];
                    }
                    out_len_need++;
                    if(out && out_len_need < out_len){
                        out[out_len_need] = in[i];
                    }
                    break;
            }
        }
        else{
            if(out && out_len_need < out_len){
                out[out_len_need] = in[i];
            }
        }
        out_len_need++;
    }

    return out_len_need;
}

static size_t str_escape(const char* in, size_t in_len, char *out, size_t out_len)
{
    size_t out_len_need = 0;
    size_t i = 0;

    for (i = 0; i < in_len; i++) {
        switch(in[i]){
            case '\"': case '\\': case '/' :
                if(out && (out_len_need+1) < out_len){
                    out[out_len_need] = '\\';
                    out[out_len_need+1] = in[i];
                }
                out_len_need += 2;
                break;
            case '\b':
                if(out && (out_len_need+1) < out_len){
                    out[out_len_need] = '\\';
                    out[out_len_need+1] = 'b';
                }
                out_len_need += 2;
                break;
            case '\f':
                if(out && (out_len_need+1) < out_len){
                    out[out_len_need] = '\\';
                    out[out_len_need+1] = 'f';
                }
                out_len_need += 2;
                break;
            case '\r':
                if(out && (out_len_need+1) < out_len){
                    out[out_len_need] = '\\';
                    out[out_len_need+1] = 'r';
                }
                out_len_need += 2;
                break;
            case '\n':
                if(out && (out_len_need+1) < out_len){
                    out[out_len_need] = '\\';
                    out[out_len_need+1] = 'n';
                }
                out_len_need += 2;
                break;
            case '\t':
                if(out && (out_len_need+1) < out_len){
                    out[out_len_need] = '\\';
                    out[out_len_need+1] = 't';
                }
                out_len_need += 2;
                break;
            default:
                if(out && (out_len_need) < out_len){
                    out[out_len_need] = in[i];
                }
                out_len_need += 1;
                break;
        }
    }

    return out_len_need;
}

#define TOKEN_VALID(pt) \
    (pt != NULL && ((pt)->type >= JSMN_OBJECT && (pt)->type <= JSMN_PRIMITIVE))
/**
 * @brief   Jump to next same level token.
 * @details
 * @return  Next token
 */
static const jsmitok_t* tok_next(const jsmitok_t* t)
{
    int range = 1;
    while(range > 0 && TOKEN_VALID(t)){
        range += t->size;
        t++; range--;
    }
    if(TOKEN_VALID(t))
        return t;
    else
        return NULL;
}
#if 0
static bool is_double(const char *data, size_t len)
{
    int i = 0;
    for(i = 0; i < len; i++){
        if(data[i] == '.'){
            return true;
        }else if(!isdigit((int)data[i])){
            return false;
        }
    }
    return false;
}
#endif
extern jsmi_parser_t jsmi_parser_create(const char *js, size_t js_len);
jsmi_parser_t jsmi_parser_create(const char *js, size_t js_len)
{
    int ret = 0;
    jsmitok_t *tok_array_temp;

    if(!js || js_len < 3)
        return NULL;

    jsmi_parser_t parser = mible_malloc(sizeof(struct jsmi_parser));
    if(!parser)
        return NULL;

    parser->js = js;
    parser->js_len = js_len;

    int tok_array_len = JSMI_PARSER_TOK_NUM_MIN;
    parser->tok_array = mible_malloc(sizeof(jsmitok_t)*(tok_array_len+1));
    if(!parser->tok_array) {
        ret = -2;
        goto _JSMI_PARSE_CREATE_DONE;
    }

    jsmn_init(&parser->jsmn_psr);

    while((parser->tok_array_len = jsmn_parse(&parser->jsmn_psr, parser->js, parser->js_len,
                    parser->tok_array, tok_array_len)) == JSMN_ERROR_NOMEM) {
        tok_array_len += JSMI_PARSER_TOK_NUM_STEP;
        if(tok_array_len > JSMI_PARSER_TOK_NUM_MAX){
            ret = -3;
            goto _JSMI_PARSE_CREATE_DONE;
        }

        tok_array_temp = parser->tok_array;
        //realloc
        if(parser->tok_array){
            mible_free(parser->tok_array);
            parser->tok_array = NULL;
        }
        parser->tok_array = mible_malloc(sizeof(jsmitok_t)*(tok_array_len+1));
        if(NULL == parser->tok_array){
            parser->tok_array = tok_array_temp;
            ret = -4;
            goto _JSMI_PARSE_CREATE_DONE;
        }
    }

    if(parser->tok_array_len < 0){
        ret = -5;
        goto _JSMI_PARSE_CREATE_DONE;
    }

    parser->tok_array[parser->tok_array_len].type = JSMN_UNDEFINED; /* tail flag */

_JSMI_PARSE_CREATE_DONE:
    if (ret) {
        jsmi_parser_delete(parser);
        parser = NULL;
    }

    return parser;
}

int jsmi_parser_delete(jsmi_parser_t parser)
{
    if(!parser)
        return -1;

    if(parser->tok_array)
        mible_free(parser->tok_array);

    mible_free(parser);

    return 0;
}

const jsmitok_t* jsmi_key_value(jsmi_parser_t parser, const jsmitok_t *parent, const char *key)
{
    if(!parser || !key)
        return NULL;

    const jsmitok_t *t = parent == NULL ? parser->tok_array : parent;
    int pairs = t->size;
    int key_len = strlen(key);

    /* is in object and the object isn't empty */
    if(t->type != JSMN_OBJECT || pairs == 0)
        return NULL;

    t += 1;
    while(t && pairs--) {
        if((key_len == JSMI_TOKLEN(t))
                && (0 == strncmp(key, parser->js + t->start, key_len))) {
            /* found the value */
            return t+1;
        }
        t = tok_next(t+1);
    }

    return NULL;
}

const jsmitok_t* jsmi_array_value(jsmi_parser_t parser, const jsmitok_t *array_tok, size_t idx)
{
    const jsmitok_t *t = array_tok;
    uint32_t num = t->size;

    if(t->type != JSMN_ARRAY || num == 0 || idx >= num)
        return NULL;

    t += 1; /* skip ']' */

    while(idx > 0 && t != NULL) {
        t = tok_next(t);
        idx--;
    }

    return t;
}

/**
 * @brief search value by key and parse as string.
 *
 * @param str_buf - buffer string
 *
 * @return 0 - ok
 */
int jsmi_get_value_str(jsmi_parser_t parser, const jsmitok_t *parent, const char *key, char *str_buf, size_t buf_sz)
{
    if(!str_buf || !(buf_sz > 0))
        return -1;

    const jsmitok_t *t = NULL;
    if (key) /* find the value token */
        t = jsmi_key_value(parser, parent, key);
    else
        t = parent;

    if (t && (t->type == JSMN_STRING)) {
        /* got valid string, copy it */
        int str_len = str_unescape(&(parser->js[t->start]), JSMI_TOKLEN(t), str_buf, buf_sz-1);
        if(str_len > buf_sz-1)
            str_len = buf_sz-1;
        str_buf[str_len] = '\0';
        return 0;
    }

    return -2;
}

/**
 * @brief parse uint64_t value from tok.
 *
 * @param parent - if NULL, find in current layer.
 * @param key - if NULL, parse from tok.
 * @param value - if NULL, valid check.
 *
 * @return  0 - ok
 */
int jsmi_get_value_uint64(jsmi_parser_t parser, const jsmitok_t *parent, const char *key, uint64_t *value)
{
    const jsmitok_t *t = NULL;
    if (key) /* find the value token */
        t = jsmi_key_value(parser, parent, key);
    else
        t = parent;

    if (t && (t->type == JSMN_PRIMITIVE && isdigit((int)(parser->js[t->start])))) {
        /* got valid int, copy if needed */
        if(value) {
            if(util_atou64n((const char *)&(parser->js[t->start]), JSMI_TOKLEN(t), value))
                return -2;
        }

        return 0;
    }

    return -1;
}

int jsmi_get_value_int64(jsmi_parser_t parser, const jsmitok_t *parent, const char *key, int64_t *value)
{
    const jsmitok_t *t = NULL;
    if (key) /* find the value token */
        t = jsmi_key_value(parser, parent, key);
    else
        t = parent;

    if (t && t->type == JSMN_PRIMITIVE && (isdigit((int)(parser->js[t->start])) || parser->js[t->start] == '-')) {
        /* got valid int, copy if needed */
        if(value) {
            if(util_atoi64n((const char *)&(parser->js[t->start]), JSMI_TOKLEN(t), value))
                return -2;
        }

        return 0;
    }

    return -1;
}

int jsmi_get_value_uint32(jsmi_parser_t parser, const jsmitok_t *parent, const char *key, uint32_t *value)
{
    const jsmitok_t *t = NULL;
    if (key) /* find the value token */
        t = jsmi_key_value(parser, parent, key);
    else
        t = parent;

    if (t && t->type == JSMN_PRIMITIVE && (isdigit((int)(parser->js[t->start])))) {
        /* got valid int, copy if needed */
        if(value) {
            if(util_atou32n((const char *)&(parser->js[t->start]), JSMI_TOKLEN(t), value))
                return -2;
        }

        return 0;
    }

    return -1;
}

int jsmi_get_value_int32(jsmi_parser_t parser, const jsmitok_t *parent, const char *key, int32_t *value)
{
    const jsmitok_t *t = NULL;
    if (key) /* find the value token */
        t = jsmi_key_value(parser, parent, key);
    else
        t = parent;

    if (t && t->type == JSMN_PRIMITIVE && (isdigit((int)(parser->js[t->start])) || parser->js[t->start] == '-')) {
        /* got valid int, copy if needed */
        if(value) {
            if(util_atoi32n((const char *)&(parser->js[t->start]), JSMI_TOKLEN(t), value))
                return -2;
        }

        return 0;
    }

    return -1;
}

int jsmi_get_value_uint16(jsmi_parser_t parser, const jsmitok_t *parent, const char *key, uint16_t *value)
{
    uint32_t num = 0;
    if (0 == jsmi_get_value_uint32(parser, parent, key, &num)) {
        *value = (uint16_t)num;
        return 0;
    }
    return -1;
}

int jsmi_get_value_int16(jsmi_parser_t parser, const jsmitok_t *parent, const char *key, int16_t *value)
{
    int32_t num = 0;
    if (0 == jsmi_get_value_int32(parser, parent, key, &num)) {
        *value = (int16_t)num;
        return 0;
    }
    return -1;
}

int jsmi_get_value_uint8(jsmi_parser_t parser, const jsmitok_t *parent, const char *key, uint8_t *value)
{
    uint32_t num = 0;
    if (0 == jsmi_get_value_uint32(parser, parent, key, &num)) {
        *value = (uint8_t)num;
        return 0;
    }
    return -1;
}

int jsmi_get_value_int8(jsmi_parser_t parser, const jsmitok_t *parent, const char *key, int8_t *value)
{
    int32_t num = 0;
    if (0 == jsmi_get_value_int32(parser, parent, key, &num)) {
        *value = (int8_t)num;
        return 0;
    }
    return -1;
}

int jsmi_get_value_bool(jsmi_parser_t parser, const jsmitok_t *parent, const char *key, bool *value)
{
    const jsmitok_t *t = NULL;
    if (NULL != (t = jsmi_key_value(parser, parent, key))) {
        if(t->type != JSMN_PRIMITIVE)
            return -1;

        if(value) {
            if(strncmp(&parser->js[t->start], "true", 4) == 0) {
                *value = true;
            } else if(strncmp(&parser->js[t->start],"false", 5) == 0) {
                *value = false;
            } else {
                return -1;
            }
            return 0;
        }
    }

    return -2;
}

int jsmi_get_value_double(jsmi_parser_t parser,  const jsmitok_t *parent, const char *key, double *value)
{
    const jsmitok_t *t = NULL;
    if (key) /* find the value token */
        t = jsmi_key_value(parser, parent, key);
    else
        t = parent;

    if (t && t->type == JSMN_PRIMITIVE && (isdigit((int)(parser->js[t->start])) || parser->js[t->start] == '-')) {
        /* got valid int, copy if needed */
        if(value) {
            if(util_atofn((const char *)&(parser->js[t->start]), JSMI_TOKLEN(t), value))
                return -2;
        }

        return 0;
    }

    return -1;
}

/* --------------------------------------------------------------------- composer */
jsmi_composer_t jsmi_composer_create(char *js, size_t js_size)
{
    if(!js || !js_size)
        return NULL;

    jsmi_composer_t composer = mible_malloc(sizeof(struct jsmi_composer));
    if(!composer)
        return NULL;

    composer->js = js;
    composer->js_size = js_size;
    composer->js_len = 0;

    return composer;
}

int jsmi_composer_delete(jsmi_composer_t composer)
{
    if(!composer)
        return -1;

    mible_free(composer);
    return 0;
}

#define COMPOSER_HAVE_MORE(composer, sz) ((composer)->js_len+(sz) < (composer)->js_size)
#define COMPOSER_LEFT(composer) ((composer)->js_size - (composer)->js_len)

#define COMPOSER_APPEND_BRACE(composer, c, more) do { \
    uint8_t len = (more) ? 2 : 1; \
    if ((composer) && COMPOSER_HAVE_MORE((composer), len)) { \
        (composer)->js[(composer)->js_len++] = (c); \
        if ((more)) \
            (composer)->js[(composer)->js_len++] = ','; \
        (composer)->js[(composer)->js_len] = '\0'; \
        return 0; \
    } \
    return -1; \
} while(0)

int jsmi_set_object_begin(jsmi_composer_t composer)
{
    COMPOSER_APPEND_BRACE(composer, '{', 0);
}
int jsmi_set_object_end(jsmi_composer_t composer, bool has_more)
{
    COMPOSER_APPEND_BRACE(composer, '}', has_more);
}

int jsmi_set_array_begin(jsmi_composer_t composer)
{
    COMPOSER_APPEND_BRACE(composer, '[', 0);
}
int jsmi_set_array_end(jsmi_composer_t composer, bool has_more)
{
    COMPOSER_APPEND_BRACE(composer, ']', has_more);
}

int jsmi_set_comma(jsmi_composer_t composer)
{
    COMPOSER_APPEND_BRACE(composer, ',', 0);
}

int jsmi_set_key(jsmi_composer_t composer, const char *key)
{
    if (key) {
        int len = snprintf(NULL, 0, "\"%s\":", key);
        if(!COMPOSER_HAVE_MORE(composer, len))
            return -1;
        composer->js_len += snprintf(composer->js+composer->js_len, COMPOSER_LEFT(composer), "\"%s\":", key);
        return 0;
    }
    return -2;
}

int jsmi_set_key_value_str(jsmi_composer_t composer, const char *key, const char* value, bool has_more)
{
    if(!composer || !value)
        return -1;

    /* set key if have */
    jsmi_set_key(composer, key);

    size_t value_len = strlen(value);

    size_t ext_len = str_escape(value, value_len, NULL, 0);
    if (has_more)
        ext_len += sizeof("\"\",") - 1;
    else
        ext_len += sizeof("\"\"") - 1;

    if (COMPOSER_HAVE_MORE(composer, ext_len)) {
        composer->js[composer->js_len++] = '\"';
        composer->js_len += str_escape(value, value_len, composer->js+composer->js_len, ext_len);
        composer->js[composer->js_len++] = '\"';
        if (has_more)
            composer->js[composer->js_len++] = ',';
        composer->js[composer->js_len] = '\0';
        return 0;
    }

    return -2;
}

int jsmi_set_sub_jsmi_str(jsmi_composer_t composer, const char* value, bool has_more)
{
    if(!composer || !value)
        return -1;

    size_t value_len = strlen(value);

    if (COMPOSER_HAVE_MORE(composer, value_len)) {
        memcpy(&composer->js[composer->js_len], value, value_len);
        composer->js_len += value_len;
        if (has_more)
            composer->js[composer->js_len++] = ',';
        composer->js[composer->js_len] = '\0';
        return 0;
    }

    return -2;
}

#define COMPOSER_APPEND_KEY_INT(composer, key, value, size, fmt) do { \
    if(!(composer)) \
        return -1; \
 \
    /* set key if have */ \
    jsmi_set_key((composer), (key)); \
 \
    if (COMPOSER_HAVE_MORE((composer), (size))) { \
        composer->js_len += snprintf(composer->js+composer->js_len, COMPOSER_LEFT((composer)), (fmt), (value)); \
        return 0; \
    } \
 \
    return -2; \
} while(0)

int jsmi_set_key_value_uint64(jsmi_composer_t composer, const char *key, uint64_t value, bool has_more)
{
    char str[32] = {0};
    size_t ext_len;

    if(!composer)
        return -1;

    /* set key if have */
    jsmi_set_key(composer, key);

    util_u64toa(value, str);

    ext_len = strlen(str);
    if(has_more)
        ext_len += sizeof(",") - 1;

    if (COMPOSER_HAVE_MORE(composer, ext_len)) {
        const char *fmt = has_more ? "%s," : "%s";
        composer->js_len += snprintf(composer->js+composer->js_len, COMPOSER_LEFT(composer), fmt, str);
        return 0;
    }

    return -2;
}
int jsmi_set_key_value_int64(jsmi_composer_t composer, const char *key, int64_t value, bool has_more)
{
    char str[32] = {0};
    size_t ext_len;

    if(!composer)
        return -1;

    /* set key if have */
    jsmi_set_key(composer, key);

    util_i64toa(value, str);

    ext_len = strlen(str);
    if(has_more)
        ext_len += sizeof(",") - 1;

    if (COMPOSER_HAVE_MORE(composer, ext_len)) {
        const char *fmt = has_more ? "%s," : "%s";
        composer->js_len += snprintf(composer->js+composer->js_len, COMPOSER_LEFT(composer), fmt, str);
        return 0;
    }

    return -2;
}

int jsmi_set_key_value_uint32(jsmi_composer_t composer, const char *key, uint32_t value, bool has_more)
{
    const char *fmt = has_more ? "%u," : "%u";
    COMPOSER_APPEND_KEY_INT(composer, key, value, 10, fmt);
}
int jsmi_set_key_value_int32(jsmi_composer_t composer, const char *key, int32_t value, bool has_more)
{
    const char *fmt = has_more ? "%d," : "%d";
    COMPOSER_APPEND_KEY_INT(composer, key, value, 11, fmt);
}

int jsmi_set_key_value_uint16(jsmi_composer_t composer, const char *key, uint16_t value, bool has_more)
{
    const char *fmt = has_more ? "%u," : "%u";
    COMPOSER_APPEND_KEY_INT(composer, key, value, 5, fmt);
}
int jsmi_set_key_value_int16(jsmi_composer_t composer, const char *key, int16_t value, bool has_more)
{
    const char *fmt = has_more ? "%d," : "%d";
    COMPOSER_APPEND_KEY_INT(composer, key, value, 6, fmt);
}

int jsmi_set_key_value_uint8(jsmi_composer_t composer, const char *key, uint8_t value, bool has_more)
{
    const char *fmt = has_more ? "%u," : "%u";
    COMPOSER_APPEND_KEY_INT(composer, key, value, 3, fmt);
}
int jsmi_set_key_value_int8(jsmi_composer_t composer, const char *key, int8_t value, bool has_more)
{
    const char *fmt = has_more ? "%d," : "%d";
    COMPOSER_APPEND_KEY_INT(composer, key, value, 4, fmt);
}

int jsmi_set_key_value_bool(jsmi_composer_t composer, const char *key, bool value, bool has_more)
{
    if(!composer)
        return -1;

    /* set key if have */
    jsmi_set_key(composer, key);

    char *val_str = value == true ? "true" : "false";

    if (COMPOSER_HAVE_MORE(composer, 5)) {
        const char *fmt = has_more ? "%s," : "%s";
        composer->js_len += snprintf(composer->js+composer->js_len, COMPOSER_LEFT(composer), fmt, val_str);
        return 0;
    }
    return -2;
}

int jsmi_set_key_value_double(jsmi_composer_t composer, const char *key, double value, bool has_more)
{
    const char *fmt = has_more ? "%f," : "%f";
    COMPOSER_APPEND_KEY_INT(composer, key, value, 18, fmt);
}
